﻿using lopet.data;
using lopet.Repositories;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//using TCPClientSample;

namespace DiffisionWithDecomposition
{
    public class Communication
    {
        public int NumberOf1sSent { get; set; }
        public int NumberOf0sSent { get; set; }
        public int NumberOf1sCorrectlyReceived { get; set; }
        public int NumberOf0sCorrectlyReceived { get; set; }
        
        public List<Molecule> Molecules = new List<Molecule>();
        public Cell Cell = new Cell(new Point(SimParameters.XCoordinateOfCellPosition, SimParameters.YCoordinateOfCellPosition, SimParameters.ZCoordinateOfCellPosition), 10);               
 
        private ExponentialDistributionRepository _expD;
        public StreamWriter _streamWiter { get; set; }
        //public Client _connectomeClient { get; set; }

        public Communication()
        {
            Simulation.Instance.InitializeSimulation(SimParameters.SimDuration, SimParameters.TimeTick);
            Simulation.Instance.DoOnTimeTick = DoOnTimeTick;

            var lambda = ExponentialDistributionRepository.GetLambdaGivenHalfLife(SimParameters.MoleculeHalfLife);
            _expD = new ExponentialDistributionRepository(lambda, SimParameters.SimDuration);

            Simulation.Instance.EnqueueEvent(
                new Event(Time.Instance.GetTimeAfterNanoSeconds(SimParameters.InitialWaitperiod), () => 
                    {
                        NumberOf1sSent = 0;
                        NumberOf0sSent = 0;
                        NumberOf1sCorrectlyReceived = 0;
                        NumberOf0sCorrectlyReceived = 0;
                    }));

            ReportCurrentStuff();
        }
      
        public virtual void ReportCurrentStuff()
        {
            Time.Instance.PrintTimeInSeconds();
            var moleculeCount = Molecules.Where(m => m.Absorbed == false && m.Decomposed == false).Count();
            Console.WriteLine("Active Molecule Count " + moleculeCount);

            //var mu = ShannonsMutualInformation.MutualInformation(NumberOf0sSent, NumberOf1sSent,
            //    NumberOf1sCorrectlyReceived, NumberOf0sCorrectlyReceived);

            //Console.WriteLine("0s = " + NumberOf0sSent);
            //Console.WriteLine("1s = " + NumberOf1sSent);
            //Console.WriteLine("1cr = " + NumberOf1sCorrectlyReceived);
            //Console.WriteLine("0cr = " + NumberOf0sCorrectlyReceived);

            //Console.WriteLine("I = " + mu);
            if (_streamWiter != null)
            {
                _streamWiter.Flush();                
            }

            Simulation.Instance.EnqueueEvent(
                new Event(Time.Instance.GetTimeAfterNanoSeconds(SimParameters.ReportInterval), () => ReportCurrentStuff()));
        }

        public void SendZeroBit(long symbolDuration)
        {
            //NumberOf0sSent++;

            Simulation.Instance.EnqueueEvent(
                new Event(Time.Instance.GetTimeAfterNanoSeconds(symbolDuration), () => ReceiveBit(0)));
        }

        public void SendOneBit(int numberOfMolecules, long symbolDuration)
        {
            //NumberOf1sSent++;
            
            for (int i = 0; i < numberOfMolecules; i++)
            {
                var molecule = new Molecule(new lopet.data.Environment
                {
                    MoleculeSize = SimParameters.EnvironmentMoleculeSize,
                    Temperature = SimParameters.Temperature,
                    Viscosity = SimParameters.Viscosity,
                    DiffusionCoefficient = SimParameters.DiffusionCoefficient
                }, new Point(0, 0, 0), SimParameters.MoleculeSize);
 
                Molecules.Add(molecule);

                int index = i;

                if (SimParameters.DecompositionEnabled)
                {
                    var time = Time.Instance.GetTimeAfterNanoSeconds((long)_expD.InvCDF(RandomNumberRepository.Instance.NextDouble));
                    if (time < SimParameters.SimDuration)
	                {
                        Simulation.Instance.EnqueueEvent(new Event(time, () => molecule.Decompose()));
	                }
                }
            }

            Simulation.Instance.EnqueueEvent(
                new Event(Time.Instance.GetTimeAfterNanoSeconds(symbolDuration), () => ReceiveBit(1)));
        }

        public void ReceiveBit(int intendedBitValue)
        {            
            //Console.WriteLine(intendedBitValue +" "+ " " + Cell.NumberOfAbsorbedMolecules);
            
            if (_streamWiter != null)
            {
                _streamWiter.WriteLine(intendedBitValue + " " + Cell.NumberOfAbsorbedMolecules);
            }
            Cell.NumberOfAbsorbedMolecules = 0;

            //swipe clean for faster runs
            var removelist = new List<int>();
            lock (Molecules)
            {
                for (int i = 0; i < Molecules.Count; i++)
                {
                    if (Molecules[i].Absorbed || Molecules[i].Decomposed || Molecules[i].Point.DistanceTo(Cell.Center) > SimParameters.MaxDistanceFromCellCenter)
                    {
                        Molecules.RemoveAt(i);
                        i--;
                    }
                }
            }

            var nextBit = RandomNumberRepository.Instance.NextDouble <= 0.5;
            if (nextBit)
            {
                SendOneBit(SimParameters.NumberOfMolecules, SimParameters.T_s);
            }
            else
            {
                SendZeroBit(SimParameters.T_s);
            }
            
        }

        public virtual void DoOnTimeTick()
        {
            Parallel.For(0, Molecules.Count(), i =>
            {
                if (!Molecules[i].Absorbed && !Molecules[i].Decomposed)
                {
                    Molecules[i].Point = Molecules[i].CalculateNextPosition(SimParameters.StepSize);
                    var inside = Cell.IsMoleculeInside(Molecules[i]);
                    if (inside)
                    {
                        Cell.AbsorbMolecule();
                        Molecules[i].Absorbed = true;
                    }
                }
            });

            //Console.WriteLine(Cell.NumberOfAbsorbedMolecules + " " + Time.Instance.Now);
            //Console.WriteLine(Molecules.Count(m => m.Decomposed == false) + " " + Time.Instance.Now);

            //Console.WriteLine(Molecules[65].Point.X + " " + Molecules[65].Point.Y + " " + Molecules[65].Point.Z);
        }
    }
}
